/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.solutions.delivery.google;

import br.com.solutions.delivery.entity.City;
import br.com.solutions.delivery.entity.Coordinate;
import br.com.solutions.delivery.entity.Location;
import br.com.solutions.delivery.entity.Route;
import br.com.solutions.delivery.entity.ZipCode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.awt.Image;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 *
 * @author wgluck
 * 
 * Classe responsável pelo acesso a API do Google Maps
 * 
 */
public class GoogleMapsController {
        
    /**
     * Serviços utilizados
     * 
     * Geocoding - recuperação dos dados do endereço (Latitude, Longitude)
     * DistanceMatrix - cálculo de distância e tempo dos trajetos
     * StaticMapURL - mapa estático do Google Maps
     */
    public static final String GEOCODING_URL = "http://maps.googleapis.com/maps/api/geocode/json?";
    public static final String DISTANCE_MATRIX_URL = "http://maps.googleapis.com/maps/api/distancematrix/json?";
    public static final String STATIC_MAP_URL = "http://maps.googleapis.com/maps/api/staticmap?";    

    /**
     * Envia a requisição para a Google de acordo com a url passada (Genérico)
     * 
     * @param url
     * @return
     * @throws Exception 
     */
    private static String sendRequisition(String url) throws Exception {
        
        // Ciando a Instancia.  
        HttpClient client = new HttpClient();
        // criando método.  
        GetMethod method = new GetMethod(url);
        // entrada necessaria  
        //method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
        //        new DefaultHttpMethodRetryHandler(3, false));
        // executa o metodo.  
        int statusCode = client.executeMethod(method);

        if (statusCode != HttpStatus.SC_OK) {
            System.err.println("Erro no !: " + method.getStatusLine());
        }
        // lendo a resposta. 
        String pagina = getStringFromInputStream(method.getResponseBodyAsStream());

        return pagina;
    }
    
    private static String getStringFromInputStream(InputStream is) {
 
		BufferedReader br = null;
		StringBuilder sb = new StringBuilder();
 
		String line;
		try {
 
			br = new BufferedReader(new InputStreamReader(is));
			while ((line = br.readLine()) != null) {
				sb.append(line);
			}
 
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
 
		return sb.toString();
 
	}
    /**
     * Retorna a localização do endereço passado
     * 
     * @param rua
     * @param numero
     * @param cidade
     * @param estado
     * @param cep
     * @return
     * @throws Exception 
     */
    public static Location getLocation(String rua, String numero, City cidade, String estado, String cep) throws Exception {
        Location location = null;
        try {
            //gera a URL
            String url = getGeoCodingUrl(rua, numero, cidade, estado);        
            //retorna a página resultado
            String resultado = sendRequisition(url);       
            //recupera a localização com as coordenadas preenchidas
            location = getCordinateLocationFromJSON(new JSONObject(resultado));
            //cria o zipcode       
            ZipCode zipCode = new ZipCode(); 
            zipCode.setZipCode(cep);
            zipCode.setCity(cidade);        
            //seta as informações adicionais da location
            location.setStreet(rua); 
            location.setZipCode(zipCode);   
        } catch (Exception e) {
            e.printStackTrace();
        }         
        return location;       
    }   
    
    /**
     * Cria a URL para enviar a solicitação a API de geolocalização
     * 
     * @param rua
     * @param numero
     * @param cidade
     * @param estado
     * @return 
     */
    private static String getGeoCodingUrl(String rua, String numero, City cidade, String estado) {
        
        //remove espaços dos nomes
        rua = rua.replaceAll(" ", "");
        numero = numero.replaceAll(" ","");
        cidade.setName(cidade.getName().replaceAll(" ", ""));
        estado = estado.replaceAll(" ","");
        
        //monta e retorna a url da API de geolocalização
        return GoogleMapsController.GEOCODING_URL + "address=" + rua + "+"
                + numero  + "+"
                + cidade.getName()  + "+"
                + estado  + "&"
                + "sensor=false";
    }
    
    /**
     * Recupera a latitude e longitude do Json de resposta da API de geolocalização
     * 
     * @param json
     * @return 
     */
    private static Location getCordinateLocationFromJSON(JSONObject json) throws JSONException {
        
        Location location = new Location();
        //recupera latitude e longitude via JSON
        JSONArray jsonResult = (JSONArray) json.get("results");
        JSONObject jsonResultLineOne = jsonResult.getJSONObject(0);
        JSONObject jsonGeometry = jsonResultLineOne.getJSONObject("geometry"); 
        JSONObject jsonLocation = jsonGeometry.getJSONObject("location");
        Double latitude = jsonLocation.getDouble("lat");
        Double longitude = jsonLocation.getDouble("lng");
        //Cria as coordenadas
        Coordinate coordinateLatitude = new Coordinate();
        coordinateLatitude.setCoordinate(latitude);
        Coordinate coordinateLongitude = new Coordinate();
        coordinateLongitude.setCoordinate(longitude);
        //seta os valores
        location.setLatitude(coordinateLatitude);
        location.setLongitude(coordinateLongitude);
        
        return location;
    }
    
    /**
     * Retorna a melhor rota calculada dos dois endereços passados
     * 
     * @param from
     * @param to
     * @return 
     */
    public Route getRoute(Location from, Location to) throws Exception {
        Route rota = null;
        try {
            String url = this.getDistanceMatrixUrl(from, to);
            String resultado = this.sendRequisition(url);
            rota = this.getRouteFromJSON(new JSONObject(resultado));
            rota.setFrom(from);
            rota.setTo(to);
            rota.setDone(Boolean.FALSE); 
        } catch (Exception e) {
            e.printStackTrace();
        }        
        return rota;
    } 
    
    /**
     * Monta a URL do serviço API Distance Matrix
     * 
     * @param from
     * @param to
     * @return 
     */
    private String getDistanceMatrixUrl(Location from, Location to) {
        
        String url = GoogleMapsController.DISTANCE_MATRIX_URL;
        //monta a URL
        url += "origins="
                + from.getLatitude().getCoordinate() + ","
                + from.getLongitude().getCoordinate() + "&"
                + "destinations="
                + to.getLatitude().getCoordinate() + ","
                + to.getLongitude().getCoordinate() + "&"
                + "sensor=false";
        
        return url;                
    }
    
    /**
     * Método para retornar a Rota carregada com a distância e a duração da viagem
     * 
     * @param json
     * @return
     * @throws JSONException 
     */
    private Route getRouteFromJSON(JSONObject json) throws JSONException {
        //Encontra Objetos com distância e duração
        JSONArray jsonRows = json.getJSONArray("rows");
        JSONObject jsonRowsLineOne = jsonRows.getJSONObject(0);
        JSONArray jsonElements = jsonRowsLineOne.getJSONArray("elements");
        JSONObject jsonElementsLineOne = jsonElements.getJSONObject(0);
        JSONObject jsonDuration = jsonElementsLineOne.getJSONObject("duration");
        JSONObject jsonDistance = jsonElementsLineOne.getJSONObject("distance");
        //Carrega duração (multiplica por 60.000 para transformar em milisegundos)
        //Carrega distância (divide por 1.000 para transoformar em Kilometros
        Date duration = new Date((jsonDuration.getInt("value")*60000));
        Double distance = (jsonDistance.getDouble("value")/1000);
        //Carrega informações na rota
        Route rota = new Route();
        rota.setEstimatedTime(duration);
        rota.setDistance(distance);
        return rota;
        
    }
    
    /**
     * Método que retorna um mapa contendo os pontos da viagem
     * 
     * @param rotas
     * @return 
     */
    public static  Image getMap(List<Route> rotas) {
        Image mapa = null;
        try {            
            //mapa = this.sendRequisition(this.getStaticMapUrl(rotas));                       
            URL url = new URL(getStaticMapUrl(rotas));            
            mapa = ImageIO.read(url);
                
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mapa;
    }
    
    /**
     * Método que retorna o link do mapa criado de acordo com a rota passada
     * 
     * @param rotas
     * @return 
     */
    private static String getStaticMapUrl(List<Route> rotas) {
       
        //Cria a URL
        return GoogleMapsController.STATIC_MAP_URL
                + "size=500x500&"
                + getMarkersFromRoutes(rotas)
                + "sensor=false";      
    }
    
    /**
     * Recupera as marcações da lista de rotas
     * 
     * @param rotas
     * @return 
     */
    private static String getMarkersFromRoutes(List<Route> rotas) {
        String markers = "";
        int posicao = 1;
        //Percorre as rotas e adiciona cada uma no mapa, início e fim
        for (Route rota : rotas) {
                markers
                += "markers=color:red%7Clabel:" + posicao
                + "%7C" + rota.getFrom().getLatitude().getCoordinate()
                + "," + rota.getFrom().getLongitude().getCoordinate()
                + "&"
                + "markers=color:red%7Clabel:" + (posicao + 1)
                + "%7C" + rota.getTo().getLatitude().getCoordinate()
                + "," + rota.getTo().getLongitude().getCoordinate()
                + "&";
                posicao++;
        }
        return markers;
    }
    
    public static void main(String[] args) throws Exception {        
        City cidade = new City();
        cidade.setName("Blumenau");
        GoogleMapsController teste = new GoogleMapsController();
        Location location = GoogleMapsController.getLocation("Rua dos Cacadores", "2187", cidade, "Santa Catarina", "89040002");
        Location location2 = GoogleMapsController.getLocation("Rua Governador Jorge Lacerda", "2500", cidade, "Santa Catarina", "89040002");
        Location location3 = GoogleMapsController.getLocation("Rua Das Bromelias", "300", cidade, "Santa Catarina", "89040002");
        Route rota = teste.getRoute(location, location2);
        Route rota2 = teste.getRoute(location2, location3);
        List<Route> rotas = new ArrayList<>();
        rotas.add(rota);
        rotas.add(rota2);
        
        JFrame frame = new JFrame();        
        frame.setSize(500, 500);
        JLabel label = new JLabel(new ImageIcon(teste.getMap(rotas)));
        frame.add(label);
        frame.setVisible(true);
               
    } 
}
